// Copyright (C) Calum Grant 2008

#include <pthread.h>
#include <unistd.h>
#include <stdexcept>

namespace dynamic
{
	namespace api
	{
		class mutex : cg::not_copyable, cg::not_assignable
		{
			pthread_mutex_t m_mutex;

		public:
			mutex()
			{
				pthread_mutexattr_t attr;
				pthread_mutexattr_init(&attr);
				pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_RECURSIVE);
				pthread_mutex_init(&m_mutex, &attr);
			}

			~mutex()
			{
				pthread_mutex_destroy(&m_mutex);
			}

			void lock()
			{
				pthread_mutex_lock(&m_mutex);
			}

			void unlock()
			{
				pthread_mutex_unlock(&m_mutex);
			}
		};


		class thread : cg::not_assignable, cg::not_copyable
		{
			pthread_t m_thread;
			thread & operator=(const thread&);

			typedef void thread_func(void*);
			thread_func *m_fn;
			void * m_data;

			static void * thread_proc(void * param)
			{
				thread * self = static_cast<thread*>(param);
				self->m_fn(self->m_data);
				return 0;
			};

		public:
			thread() : m_thread(0) 
			{ 
			}

			void run(thread_func fn, void * data)
			{
				m_fn = fn;
				m_data = data;
				pthread_attr_t attr;
				pthread_attr_init(&attr);
				pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_JOINABLE);
				if( pthread_create(&m_thread, &attr, &thread_proc, this) )
					throw std::runtime_error("pthread_create failed");
				pthread_attr_destroy(&attr);
			}

			void join()
			{
				void * status;
				if( pthread_join(m_thread, &status) )
					throw std::runtime_error("pthread_join failed");
			}
		};


		class event : cg::not_copyable, cg::not_assignable
		{
			pthread_mutex_t m_mutex;
			pthread_cond_t m_cond;
			bool m_set;

		public:
			event()
			{
				if( pthread_cond_init(&m_cond, NULL) )
					throw std::runtime_error("pthread_cond_init");
				pthread_mutex_init(&m_mutex, NULL);
				m_set = false;
			}

			~event()
			{
				pthread_mutex_destroy(&m_mutex);
				pthread_cond_destroy(&m_cond);
			}

			void wait()
			{
				pthread_mutex_lock(&m_mutex);
				if(m_set)
				{
					m_set = false;
					pthread_mutex_unlock(&m_mutex);
					return;
				}
				if( pthread_cond_wait(&m_cond, &m_mutex) )
					throw std::runtime_error("pthread_cond_wait failed");
				m_set = false;
				pthread_mutex_unlock( &m_mutex );
			}

			void signal()
			{
				pthread_mutex_lock(&m_mutex);
				m_set = true;
				pthread_mutex_unlock(&m_mutex);
				if( pthread_cond_signal(&m_cond) )
					throw std::runtime_error("pthread_cond_signal failed");
			}
		};


		inline void sleep(int s)
		{
			::sleep(s);
		}


		/// An atomic (threadsafe) integer which supports ++ and --.
		/** \ingroup  mem */
		class atomic_counter
		{
		public:
			atomic_counter() : m_counter(0) 
			{ 
			}

			int operator++() 
			{ 
				m_mutex.lock();
				int r = ++m_counter;
				m_mutex.unlock();
				return r;
			}

			int operator++(int) 
			{ 
				m_mutex.lock();
				int r = m_counter++;
				m_mutex.unlock();
				return r;
			}

			int operator--() 
			{ 
				m_mutex.lock();
				int r = --m_counter;
				m_mutex.unlock();
				return r;
			}

			int operator--(int) 
			{ 
				m_mutex.lock();
				int r = m_counter--;
				m_mutex.unlock();
				return r;
			}

		private:
			int m_counter;
			mutex m_mutex;
		};


		class stack : cg::not_copyable, cg::not_assignable
		{
		public:
			stack() : m_size(0), m_shutdown(false)
			{
				pthread_mutex_init(&m_mutex, NULL);
				pthread_cond_init(&m_cond, NULL);
				pthread_cond_init(&m_empty, NULL);
			}

			~stack()
			{
				pthread_cond_destroy(&m_empty);
				pthread_cond_destroy(&m_cond);
				pthread_mutex_destroy(&m_mutex);
			}

			void push()
			{
				pthread_mutex_lock(&m_mutex);
				m_size++;
				pthread_cond_signal(&m_cond);
				pthread_mutex_unlock(&m_mutex);
			}

			void shutdown()
			{
				pthread_mutex_lock(&m_mutex);
				m_shutdown = true;
				pthread_cond_broadcast(&m_cond);
				pthread_mutex_unlock(&m_mutex);
			}

			bool check_shutdown()
			{
				pthread_mutex_lock(&m_mutex);
				bool ret = m_shutdown;
				pthread_mutex_unlock(&m_mutex);
				return ret;
			}

			bool wait_pop()
			{
				pthread_mutex_lock(&m_mutex);
				while(m_size == 0 && !m_shutdown) pthread_cond_wait(&m_cond, &m_mutex);
				if(m_shutdown)
				{
					pthread_mutex_unlock(&m_mutex);
					return false;
				}
				--m_size;
				if(m_size) pthread_cond_signal(&m_cond);
				else pthread_cond_signal(&m_empty);
				pthread_mutex_unlock(&m_mutex);
				return true;
			}

			void wait_empty()
			{
				pthread_mutex_lock(&m_mutex);
				while(m_size && !m_shutdown) pthread_cond_wait(&m_empty, &m_mutex);
				pthread_mutex_unlock(&m_mutex);
			}

		private:
			int m_size;
			bool m_shutdown;

			pthread_mutex_t m_mutex;
			pthread_cond_t m_cond, m_empty;
		};
	}
}
